#include <ArduinoOTA.h>
#include <Arduino_JSON.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#include <Servo.h>
#include <SparkFun_Qwiic_Humidity_AHT20.h>
#include <Ticker.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <stdint.h>

#include "EepromClass.h"

const char *ssid = "MyIoTHome";
const char *password = "12345678909";
const char *ApName = "Esp8266";
const char *ApPassword = "12345678909";

const char *HtmlRootPageFileURL = "/index.html";  // 网页主页在文件系统中的路径

#define EEP_DATA_ADDR 4
#define EEP_VER_ADDR 0

#define ENCODE_VERSION(major, minor, patch, dev) \
    ((major << 24) | (minor << 16) | (patch << 8) | dev)

#define VERSION ENCODE_VERSION(1, 0, 0, 1)

/* ***************************** Start 函数声明 ***************************** */
void handleRoot();
void handleGetMcuStatus();
void handleCtrlOutput();
void handleCloseDebugMode();
void handleChangeThr();
void handleNotFound();
void TaskHandleOutputStatus();
void TaskReadSensorData();
void TaskAnalyseStatus(void);
/* ****************************** End 函数声明 ****************************** */

enum EnumThresholdWarning
{
    eThrWarn_None,  ///< 没问题
    eThrWarn_Low,   ///< 低阈值溢出
    eThrWarn_High,  ///< 高阈值溢出
};

typedef struct _ThresholdData
{
    int8_t warning;  ///< 警报
    float low;       ///< 低阈值
    float high;      ///< 高阈值
} ThresholdTypeDef;

struct _Status
{
    uint8_t airFanPin;               ///< 风扇管脚
    uint8_t motorPin;                ///< 电机管脚
    uint8_t beepPin;                 ///< 蜂鸣器引脚
    uint8_t lampPin;                 ///< 灯引脚
    uint8_t humanDetectionPin;       ///< 人体检测管脚
    uint8_t flameDetectionPin;       ///< 火焰检测管脚
    uint8_t smogDetectionPin;        ///< 烟雾检测管脚
    uint8_t tickFlag;                ///< 滴答标志位
    uint16_t humanDetectionVal : 1;  ///< 人体检测端口值
    uint16_t flameDetectionVal : 1;  ///< 火焰检测端口值
    uint16_t smogDetectionVal : 1;   ///< 烟雾检测端口值
    uint16_t humanDetection : 1;     ///< 人体检测状态位
    uint16_t flameDetection : 1;     ///< 火焰检测状态位
    uint16_t smogDetection : 1;      ///< 烟雾检测状态位
    uint16_t airFanCtrl : 1;         ///< 风扇控制位
    uint16_t motorCtrl : 1;          ///< 电机控制位
    uint16_t beepCtrl : 1;           ///< 蜂鸣器控制位
    uint16_t lampCtrl : 1;           ///< 蜂鸣器控制位
    uint16_t debugMode : 1;  ///< 调试模式标志位,1为开启,开启后不根据传感器改变输出
    float temperature;             ///< 温度
    float humidity;                ///< 湿度
    ThresholdTypeDef temperThr;    ///< 温度阈值
    ThresholdTypeDef humidityThr;  ///< 湿度阈值
} gInfo = {0};

ESP8266WebServer webServer(80);

Servo servo;
EepromCheck<uint32_t> eepVer;
EepromCheck<struct _Status> eepStatus;
AHT20 sensor;

#define STR_BUFF_SIZE (5 * 512)
char str[STR_BUFF_SIZE];

void WriteEEPROM()
{
    uint8_t *p;
    uint16_t i, len = sizeof(struct _Status);
    uint32_t version;

    version = VERSION;
    eepVer.save(EEP_VER_ADDR, version);
    eepStatus.save(EEP_DATA_ADDR, gInfo);
}

/**
 * @brief  若检测到设备不是第一次使用,则读取EEPROM中的数据
 * @param  None
 * @retval 设备第一次使用? 1表示第一次
 */
int8_t ReadEEPROM()
{
    uint8_t *p;
    uint16_t i, len = sizeof(struct _Status);
    uint32_t version;
    int8_t retval = 0;

    eepVer.fetch(EEP_VER_ADDR, version);
    if (version == VERSION)
    {
        eepStatus.fetch(EEP_DATA_ADDR, gInfo);
    }
    else
    {
        retval = 1;
    }

    return retval;
}

void setup(void)
{
    uint8_t i = 0;
    Wire.begin(D5, D6);
    Serial.begin(115200);

    WiFi.softAP(ApName, ApPassword);
    WiFi.setAutoConnect(true);
    WiFi.setAutoReconnect(true);
    WiFi.begin(ssid, password);

    i = 6;
    while (WiFi.status() != WL_CONNECTED && i)
    {
        i--;
        delay(1000);
        Serial.print(".");
    }
    if (i == 0)
    {
        Serial.println("\r\nwifi not connected!");
    }
    else
    {
        Serial.println("\r\nwifi connected!");
        Serial.println(WiFi.localIP());
    }

    webServer.on("/", handleRoot);
    webServer.on("/controlOutput", handleCtrlOutput);
    webServer.on("/closeDebugMode", handleCloseDebugMode);
    webServer.on("/changeThreshold", handleChangeThr);
    webServer.on("/getMcuStatus", handleGetMcuStatus);
    webServer.onNotFound(handleNotFound);
    webServer.begin();

    if (!SPIFFS.begin())
    {
        Serial.println("SPIFFS begin Error!");
    }

    EEPROM.begin(512);

    if (ReadEEPROM())
    {
        // 首次开机
        initVariables();
    }
    else
    {
        // 非首次开机
    }

    gInfo.airFanCtrl = false;
    gInfo.beepCtrl = false;
    gInfo.lampCtrl = false;
    gInfo.motorCtrl = false;

    gInfo.humanDetection = false;
    gInfo.smogDetection = false;
    gInfo.flameDetection = false;

    gInfo.debugMode = false;

    pinMode(gInfo.lampPin, OUTPUT);           // 蜂鸣器
    pinMode(gInfo.beepPin, OUTPUT);           // 蜂鸣器
    pinMode(gInfo.motorPin, OUTPUT);          // 电机
    pinMode(gInfo.airFanPin, OUTPUT);         // 风扇
    pinMode(gInfo.humanDetectionPin, INPUT);  // 人体检测模块引脚
    pinMode(gInfo.flameDetectionPin, INPUT);  // 火焰检测模块引脚
    pinMode(gInfo.smogDetectionPin, INPUT);   // 烟雾检测模块引脚

    if (sensor.begin() == false)
    {
        Serial.println("sensor init failed!");
    }
}

void loop(void)
{
    static uint32_t ts = 0, lastTs;

    if ((millis() - lastTs) >= 5000)
    {
        lastTs = millis();
        Serial.printf(
            "\t\tDetection:\n"
            "human: %d\tflame: %d\tsmog: %d\ttemperature: %.2fC\tHumidity: %.2f%RH\n"
            "\t\tControl:\n"
            "airFan: %d\tmotor: %d\tbeep: %d\tlamp: %d\r\n"
            "\t\tThreshold:\n"
            "temper:{warn: %d, low: %.1f, high: %.1f}\n"
            "humidity:{warn: %d, low: %.1f, high: %.1f}\r\n",
            gInfo.humanDetection, gInfo.flameDetection, gInfo.smogDetection, gInfo.temperature,
            gInfo.humidity, gInfo.airFanCtrl, gInfo.motorCtrl, gInfo.beepCtrl, gInfo.lampCtrl,
            gInfo.temperThr.warning, gInfo.temperThr.low, gInfo.temperThr.high,
            gInfo.humidityThr.warning, gInfo.humidityThr.low, gInfo.humidityThr.high);
    }

    if ((millis() - ts) >= 20)  // 5ms执行一次
    {
        ts = millis();

        TaskReadSensorData();  // 读取传感器检测到的数据
        TaskAnalyseStatus();   // 根据传感器数据分析状态
        if (gInfo.debugMode == false)
        {
            gInfo.lampCtrl = false;
            gInfo.beepCtrl = false;
            gInfo.motorCtrl = false;
            gInfo.airFanCtrl = false;
            TaskAnalyseOutputStatus();  // 根据状态分析输出状态
        }
        TaskHandleOutputStatus();  // 根据输出状态控制输出引脚输出相应电平
    }

    webServer.handleClient();
}

/**
 * @brief  根据状态分析输出
 * @param  None
 * @retval None
 */
void TaskAnalyseOutputStatus()
{
    if (gInfo.temperThr.warning == eThrWarn_High)
    {
        gInfo.airFanCtrl = true;
        gInfo.beepCtrl = true;
    }
    else if (gInfo.temperThr.warning == eThrWarn_Low)
    {
        gInfo.motorCtrl = true;
        gInfo.beepCtrl = true;
    }

    if (gInfo.humidityThr.warning == eThrWarn_High)
    {
        gInfo.motorCtrl = true;
        gInfo.beepCtrl = true;
    }
    else if (gInfo.humidityThr.warning == eThrWarn_Low)
    {
        gInfo.airFanCtrl = true;
        gInfo.beepCtrl = true;
    }

    if (gInfo.humanDetection)
    {
        gInfo.lampCtrl = true;
        gInfo.beepCtrl = true;
    }
    if (gInfo.smogDetection)
    {
        gInfo.lampCtrl = true;
        gInfo.beepCtrl = true;
        gInfo.motorCtrl = true;
    }
    if (gInfo.flameDetection)
    {
        gInfo.lampCtrl = true;
        gInfo.beepCtrl = true;
        gInfo.motorCtrl = true;
    }
}

/**
 * @brief  根据控制位来执行操作
 * @param  None
 * @retval None
 */
void TaskHandleOutputStatus()
{
    digitalWrite(gInfo.airFanPin, gInfo.airFanCtrl);
    digitalWrite(gInfo.motorPin, gInfo.motorCtrl);
    digitalWrite(gInfo.beepPin, gInfo.beepCtrl);
    digitalWrite(gInfo.lampPin, gInfo.lampCtrl);
}

/**
 * @brief  采集人体感应器的数据
 * @note   检测到引脚跳变后会进行1000ms的消抖处理
 * @param  None
 * @retval None
 */
void TaskAnalyseStatus(void)
{
    static uint16_t humanCnt = 0, flameCnt = 0, smogCnt = 0;

    if (gInfo.temperature < gInfo.temperThr.low)
    {
        gInfo.temperThr.warning = eThrWarn_Low;
    }
    else if (gInfo.temperature > gInfo.temperThr.high)
    {
        gInfo.temperThr.warning = eThrWarn_High;
    }
    else
    {
        /* 温度正常 */
        gInfo.temperThr.warning = eThrWarn_None;
    }

    if (gInfo.humidity < gInfo.humidityThr.low)
    {
        gInfo.humidityThr.warning = eThrWarn_Low;
    }
    else if (gInfo.humidity > gInfo.humidityThr.high)
    {
        gInfo.humidityThr.warning = eThrWarn_High;
    }
    else
    {
        /* 湿度正常 */
        gInfo.humidityThr.warning = eThrWarn_None;
    }

    if (gInfo.humanDetectionVal == 0)
    {
        humanCnt = 0;
        gInfo.humanDetection = false;
    }
    else
    {
        humanCnt++;
        if (humanCnt >= 5)
        {
            humanCnt = 5;
            gInfo.humanDetection = true;
        }
    }
    if (gInfo.flameDetectionVal == 1)
    {
        flameCnt = 0;
        gInfo.flameDetection = false;
    }
    else
    {
        flameCnt++;
        if (flameCnt >= 5)
        {
            flameCnt = 5;
            gInfo.flameDetection = true;
        }
    }
    if (gInfo.smogDetectionVal == 1)
    {
        smogCnt = 0;
        gInfo.smogDetection = false;
    }
    else
    {
        smogCnt++;
        if (smogCnt >= 5)
        {
            smogCnt = 5;
            gInfo.smogDetection = true;
        }
    }
}

void TaskReadSensorData()
{
    gInfo.humanDetectionVal = digitalRead(gInfo.humanDetectionPin);
    gInfo.flameDetectionVal = digitalRead(gInfo.flameDetectionPin);
    gInfo.smogDetectionVal = digitalRead(gInfo.smogDetectionPin);

    if (sensor.available() == true)
    {
        gInfo.humidity = sensor.getHumidity();
        gInfo.temperature = sensor.getTemperature();
    }
    else  // 设备掉线
    {
    }
}

void initVariables()
{
    gInfo.flameDetectionPin = D3;
    gInfo.smogDetectionPin = D1;
    gInfo.humanDetectionPin = D4;
    gInfo.lampPin = D0;
    gInfo.beepPin = D2;
    gInfo.motorPin = D8;
    gInfo.airFanPin = D7;
    gInfo.temperThr.low = 20.0;
    gInfo.temperThr.high = 30.0;
    gInfo.humidityThr.low = 30.0;
    gInfo.humidityThr.high = 60.0;
}

void handleRoot()
{
    File myFile;
    myFile = SPIFFS.open(HtmlRootPageFileURL, "r");
    if (myFile)
    {
        Serial.printf("Root. SPIFFS Success! File size is:%d\n", myFile.size());
        webServer.send(200, "text/html", myFile.readString());
        myFile.close();
    }
    else
    {
        Serial.println("Root. SPIFFS Error!");
        webServer.send(200, "text/plain", "Please check your SPIFFS!");
    }
}

void handleGetMcuStatus()
{
    JSONVar obj;
    String message;

    snprintf(
        str, STR_BUFF_SIZE, "<p>温度:%.1f℃,湿度:%.1f%RH</p>%s%s%s%s%s%s%s%s", gInfo.temperature,
        gInfo.humidity,
        gInfo.humanDetection ? "<p style='color:red;'>检测到仓库有人</p>" : "<p>未检测到人</p>",
        gInfo.smogDetection ? "<p style='color:red;'>检测到有烟雾</p>" : "<p>未检测到烟雾</p>",
        gInfo.flameDetection ? "<p style='color:red;'>检测到有火焰</p>" : "<p>未检测到火焰</p>",
        (gInfo.temperThr.warning == eThrWarn_High) ? "<p style='color:red;'>温度过高</p>" : "",
        (gInfo.temperThr.warning == eThrWarn_Low) ? "<p style='color:red;'>温度过低</p>" : "",
        (gInfo.humidityThr.warning == eThrWarn_High) ? "<p style='color:red;'>湿度过高</p>" : "",
        (gInfo.humidityThr.warning == eThrWarn_Low) ? "<p style='color:red;'>湿度过低</p>" : "",
        gInfo.debugMode ? "<p style='color:yellow;'>当前处于调试模式</p>"
                        : "<p>当前处于运行模式</p>");
    obj["status"]["warehouse"] = str;
    obj["status"]["humanDetection"] = gInfo.humanDetection;
    obj["status"]["smogDetection"] = gInfo.smogDetection;
    obj["status"]["flameDetection"] = gInfo.flameDetection;
    obj["status"]["aitFanCtrl"] = gInfo.airFanCtrl;
    obj["status"]["motorCtrl"] = gInfo.motorCtrl;
    obj["status"]["beepCtrl"] = gInfo.beepCtrl;
    obj["status"]["lampCtrl"] = gInfo.lampCtrl;
    obj["status"]["THR"]["temperature"]["low"] = gInfo.temperThr.low;
    obj["status"]["THR"]["temperature"]["high"] = gInfo.temperThr.high;
    obj["status"]["THR"]["humidity"]["low"] = gInfo.humidityThr.low;
    obj["status"]["THR"]["humidity"]["high"] = gInfo.humidityThr.high;

    message = JSON.stringify(obj);
    // Serial.println(message);
    webServer.send(200, "text/plain", message);
}

void handleCtrlOutput()
{
    String message;
    JSONVar obj;
    int ctrlNum, value = 0, err = 0;

    obj = JSON.parse(webServer.arg(0));
    ctrlNum = (int)obj["num"];
    value = (int)obj["value"];
    switch (ctrlNum)
    {
    case 1:
        gInfo.airFanCtrl = value;
        gInfo.debugMode = true;
        break;
    case 2:
        gInfo.motorCtrl = value;
        gInfo.debugMode = true;
        break;
    case 3:
        gInfo.beepCtrl = value;
        gInfo.debugMode = true;
        break;
    case 4:
        gInfo.lampCtrl = value;
        gInfo.debugMode = true;
        break;
    default:
        err = 1;
        break;
    }
    obj = NULL;
    if (err == 0)
    {
        obj["Control Output"]["state"] = "success";
        obj["Control Output"]["err info"] = "";
    }
    else
    {
        obj["Control Output"]["state"] = "failed";
        obj["Control Output"]["err info"] = "Num err!";
    }

    message = JSON.stringify(obj);
    Serial.printf("num:%d,value:%d\n", ctrlNum, value);
    Serial.println(message);
    webServer.send(200, "text/plain", message);
}

void handleCloseDebugMode()
{
    String message;
    JSONVar obj;

    gInfo.debugMode = false;

    obj = NULL;
    obj["Close Debug Mode"]["state"] = "success";
    obj["Close Debug Mode"]["err info"] = "";

    message = JSON.stringify(obj);
    Serial.println(message);
    webServer.send(200, "text/plain", message);
}

void handleChangeThr()
{
    String message;
    JSONVar obj;
    int err = 0;

    obj = JSON.parse(webServer.arg(0));
    gInfo.temperThr.low = (double)obj["temperature"]["low"];
    gInfo.temperThr.high = (double)obj["temperature"]["high"];
    gInfo.humidityThr.low = (double)obj["humidity"]["low"];
    gInfo.humidityThr.high = (double)obj["humidity"]["high"];

    WriteEEPROM();

    obj = NULL;
    if (err == 0)
    {
        obj["Control Output"]["state"] = "success";
        obj["Control Output"]["err info"] = "";
    }
    else
    {
        obj["Control Output"]["state"] = "failed";
        obj["Control Output"]["err info"] = "Num err!";
    }

    message = JSON.stringify(obj);
    Serial.printf("T_L:%.1f,T_H:%.1f; H_L:%.1f,H_H:%.1f\n", gInfo.temperThr.low,
                  gInfo.temperThr.high, gInfo.humidityThr.low, gInfo.humidityThr.high);
    Serial.println(message);
    webServer.send(200, "text/plain", message);
}

void handleNotFound()
{
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += webServer.uri();
    message += "\nMethod: ";
    message += (webServer.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += webServer.args();
    message += "\n";

    for (uint8_t i = 0; i < webServer.args(); i++)
    {
        message += " " + webServer.argName(i) + ": " + webServer.arg(i) + "\n";
    }

    webServer.send(404, "text/plain", message);
}
